package com.atguigu.tingshu.account.service.impl;

import java.util.Date;

import com.atguigu.tingshu.account.mapper.UserAccountDetailMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.google.common.collect.Maps;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.account.mapper.UserAccountMapper;
import com.atguigu.tingshu.account.service.UserAccountService;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.model.account.UserAccount;
import com.atguigu.tingshu.model.account.UserAccountDetail;
import com.atguigu.tingshu.vo.account.AccountLockResultVo;
import com.atguigu.tingshu.vo.account.AccountLockVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class UserAccountServiceImpl extends ServiceImpl<UserAccountMapper, UserAccount> implements UserAccountService {

    @Autowired
    private UserAccountMapper userAccountMapper;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private UserAccountDetailMapper userAccountDetailMapper;

    @Override
    public void saveAccount(UserAccount userAccount) {
        try {
            userAccountMapper.insert(userAccount);
        } catch (Exception e) {
            throw new GuiguException(201, "保存账户失败");
        }
    }

    @Override
    public BigDecimal getAvailableAmount() {
        Long userId = AuthContextHolder.getUserId();
        LambdaQueryWrapper<UserAccount> userAccountLambdaQueryWrapper = new LambdaQueryWrapper<>();
        userAccountLambdaQueryWrapper.eq(UserAccount::getUserId, userId).select(UserAccount::getAvailableAmount);
        UserAccount userAccount = userAccountMapper.selectOne(userAccountLambdaQueryWrapper);
        return userAccount.getAvailableAmount();
    }

    // openfeign可能会多次调用此方法(重试机制:5次,间隔时间不同 需要程序员手动打开) 需要做幂等性处理
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<AccountLockResultVo> checkAndLockAmount(AccountLockVo userAccountVo) {
        Long userId = userAccountVo.getUserId();
        BigDecimal amount = userAccountVo.getAmount();
        String orderNo = userAccountVo.getOrderNo();
        String content = userAccountVo.getContent();
        String userAccountKey = "user:account:amount" + orderNo;
        String lockKey = "user:account:amount:lock" + orderNo;
        // 1.查询余额是否充足
        // 1.1 将判断余额和修改作为一个原子操作 利用CAS思想
        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, "1");
        if (!aBoolean) {
            // 1.2 如果获取锁失败,则直接返回锁定失败 防止业务还未处理完时 其他线程再次执行业务
            return Result.build(null, ResultCodeEnum.ACCOUNT_LOCK_REPEAT);// 锁定失败
        }
        String result = redisTemplate.opsForValue().get(userAccountKey);
        if (StringUtils.hasText(result)) {
            // 1.3 在业务线程刚刚返回时 openfeign又发起了请求 此时业务已经被执行完毕了 数据在redis中存在 直接返回
            return Result.build(JSONObject.parseObject(result, AccountLockResultVo.class), ResultCodeEnum.ACCOUNT_LOCK_REPEAT);
        }
        AccountLockResultVo accountLockResultVo;
        try {
            int count = userAccountMapper.checkAndLockAmount(userId, amount);
            if (count == 0) {
                // 2.不充足 锁定失败 将状态码设置为非200
                // 3.修改数据库锁定余额出错 将状态码设置为非200
                return Result.build(null, ResultCodeEnum.ACCOUNT_LOCK_ERROR);// 锁定失败
            }

            // 4.充足 锁定余额成功
            // 5.构建AccountLockResultVo
            accountLockResultVo = new AccountLockResultVo();
            accountLockResultVo.setUserId(userId);
            accountLockResultVo.setAmount(userAccountVo.getAmount());
            accountLockResultVo.setContent(userAccountVo.getContent());

            // 6.将锁定结果对象存储到Redis中 方便解锁和真正扣除余额

            redisTemplate.opsForValue().set(userAccountKey, JSONObject.toJSONString(accountLockResultVo));
            // 7.向user_account表中插入一条流水记录
            this.log(content, userId, amount, orderNo, "1202-锁定");
            return Result.ok(accountLockResultVo);
        } catch (Exception e) {
            // todo openfeign业务执行期间出现异常 让openfeign重试
            throw new GuiguException(400, "服务内部出现了异常");
        } finally {
            redisTemplate.delete(lockKey);
        }
    }

    @Override
    public void log(String content, Long userId, BigDecimal amount, String orderNo, String tradeType) {
        UserAccountDetail userAccountDetail = new UserAccountDetail();
        userAccountDetail.setUserId(userId);
        userAccountDetail.setTitle(content);
        userAccountDetail.setTradeType(tradeType);
        userAccountDetail.setAmount(amount);
        userAccountDetail.setOrderNo(orderNo);

        userAccountDetailMapper.insert(userAccountDetail);
    }

    @Override
    public IPage<UserAccountDetail> findUserRechargePage(IPage<UserAccountDetail> pageParam, Long userId) {
        pageParam = userAccountDetailMapper.findUserRechargePage(pageParam, userId);
        return pageParam;
    }

    @Override
    public IPage<UserAccountDetail> findUserConsumePage(IPage<UserAccountDetail> pageParam, Long userId) {
        // 调用mapper 层方法
        return userAccountDetailMapper.selectUserConsumePage(pageParam, userId);
    }
}
